Skip to content

Enhancement: Public extension manager for ECH#10568

Open
sebastian-carpenter wants to merge 5 commits into
wolfSSL:masterfrom
sebastian-carpenter:tls-ech-ext-public
Open

Enhancement: Public extension manager for ECH#10568
sebastian-carpenter wants to merge 5 commits into
wolfSSL:masterfrom
sebastian-carpenter:tls-ech-ext-public

Conversation

@sebastian-carpenter

@sebastian-carpenter sebastian-carpenter commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Description

Reworks ECH public-vs-private extension handling. The old path swapped a single SNI string in place on the live extension list - this was fragile and SNI-only. This PR generalizes it to a public-extension list on the ECH struct that's swapped by extension type, so the outer ClientHello can carry arbitrary public extensions.

ZD#21931

Public extension manager

  • Add TLSX* extensions to WOLFSSL_ECH holding the public extensions (e.g. the public-name SNI), populated in TLSX_ECH_Use from echConfig->publicName.
  • Replace TLSX_EchChangeSNI/TLSX_EchRestoreSNI with:
    • TLSX_EchShouldHideInner - true when ECH is the outer type.
    • TLSX_EchSwapExtensions - swaps matching extension types between ssl->extensions and ech->extensions; unmatched public exts are prepended; popCount reverses leading nodes to preserve OuterExtensions ordering.
    • TLSX_EchReplaceExtensions - accept: free the public list; reject: swap public exts into ssl->extensions.
  • TLSX_GetSizeWithEch/TLSX_WriteWithEch install the public exts before size/write and swap back after.
  • ForceZero the hpke; free ech->extensions in TLSX_ECH_Free.

SNI changes

  • Remove the old SNI-state machinery: drop the EchStateSNI enum and the sniState/privateName fields, plus the sniState transitions in DoTls13HandShakeMsgType.
  • TLSX_SNI_Parse: drop the sniState-based private-name save/restore. On outer-CH parse, match against any echConfig->publicName and write a matched public SNI to ech->extensions, not ssl->extensions; simplify the matched/cacheOnly and response-flag logic.
  • Inner SNI is now optional - removed the client-side "inner SNI must be present" requirement enforced by TLSX_EchChangeSNI.
  • SendTls13ClientHello: read padding length via TLSX_SNI_GetRequest instead of ech->privateName.
  • Call TLSX_EchReplaceExtensions at the accept/reject points (EchCheckAcceptance, DoTls13ServerHello reject fallback, DoTls13ClientHello). This installs the correct SNI for use later in the handshake.

Padding

  • If the client ssl had no SNI then it would take the generic no SNI path. Updated padding calculation to fallback to ctx's SNI if it is available.

Testing

  • Add test_wolfSSL_Tls13_ECH_wire_sni: drive the handshake manually and assert the public name is present and the private name absent in raw CH1/CH2 bytes, across accept/reject.
  • Update test_wolfSSL_Tls13_ECH_no_private_name: no-inner-SNI now succeeds against a permissive server (ACCEPTED); rejected only with ABORT_ON_ABSENCE.
  • Add b64MandatoryFirst config case; test_ech_server_sni_callback rejects a wrong public name; add a double-public-SNI scenario to bad_configs_ex.
  • Remove obsolete test_wolfSSL_Tls13_ECH_long_SNI (its overflow target - the deleted swap helpers - no longer exist) and relocate the over-long-SNI BAD_LENGTH_E check into test_wolfSSL_UseSNI_params.
  • Testing for the new TLSX_EchSwapExtensions function: test_TLSX_EchSwapExtensions. This emulates the swap between ssl->extensions and ech->extensions with various configs: 'match', 'no match', 'match and non-match', 'match and no-match + out of order'. This required qualifying the function with WOLFSSL_TEST_VIS.

Added testing from #10542:

  • More efficient *_wire_sni test
  • Interop testing for when ECH is rejected

Follow-ups

  • Lays groundwork for managing multiple public extensions and multiple SNI's in the outer ClientHello.

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

@sebastian-carpenter sebastian-carpenter self-assigned this Jun 1, 2026
Copilot AI review requested due to automatic review settings June 1, 2026 20:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors TLS 1.3 ECH public-vs-private extension handling by introducing a dedicated “public extensions” list on the WOLFSSL_ECH object and swapping extensions by type, replacing the previous SNI-only in-place string swap approach. It also updates SNI/ECH parsing and handshake flow to install the correct extensions on accept/reject paths, and adds/updates tests to validate wire visibility of public vs private SNI.

Changes:

  • Add WOLFSSL_ECH::extensions to hold “public” extensions and implement swapping/replacement helpers used during ClientHello size/write and accept/reject handling.
  • Rework SNI parsing and ECH handshake transitions to remove the old sniState/privateName machinery and support public-name matching for outer ClientHello.
  • Update API tests to cover new behaviors (wire-SNI visibility, no-inner-SNI behavior, new bad config cases) and relocate long-SNI length validation coverage.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
wolfssl/internal.h Removes old SNI-state fields and adds WOLFSSL_ECH::extensions plus TLSX_EchReplaceExtensions() prototype.
src/tls.c Implements public-extension swapping/replacement, updates SNI parsing, and updates ECH lifecycle management.
src/tls13.c Adjusts ClientHello padding derivation and installs correct extension sets at ECH accept/reject points (incl. HRR).
tests/api.c Adds/updates ECH/SNI tests (wire visibility, no-private-name behavior, new config cases) and relocates length checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tls.c
Comment thread src/tls.c
Comment thread tests/api.c Outdated
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

retest this please

@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-ext-public branch 2 times, most recently from 8c2fa10 to 61e1638 Compare June 9, 2026 16:25
@dgarske dgarske requested a review from Copilot June 9, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread src/tls.c
Comment thread src/tls13.c Outdated
@sebastian-carpenter

sebastian-carpenter commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Jenkins retest this please.

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Code Review

Scan type: reviewOverall recommendation: COMMENT
Findings: 4 total — 4 posted, 0 skipped
4 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] Multi-public-extension swap/reversal path is untestedsrc/tls.c:16682-16734
  • [Low] ECH padding length only considers ssl-extensions, not ctx-level inner SNIsrc/tls13.c:4844-4855
  • [Info] Pointer declaration style inconsistent with repo conventionsrc/tls.c:2378
  • [Info] TLSX_EchShouldHideInner can be a single boolean expressionsrc/tls.c:16665-16671

Review generated by Skoll

Comment thread src/tls.c Outdated
Comment thread src/tls13.c
Comment thread src/tls.c Outdated
Comment thread src/tls.c
@sebastian-carpenter sebastian-carpenter force-pushed the tls-ech-ext-public branch 2 times, most recently from 4eef82d to b4058e9 Compare June 10, 2026 16:52

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Code Review

Scan type: reviewOverall recommendation: COMMENT
Findings: 2 total — 2 posted, 0 skipped
2 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Low] TLSX_FreeAll echList tracks only a single ECH extension per listsrc/tls.c:15190-15216
  • [Low] BAD_STATE_E on restore-swap leaves ssl-extensions inconsistent without repairsrc/tls.c:16895-16901, 17111-17117

Review generated by Skoll

Comment thread src/tls.c
Comment thread src/tls.c Outdated
@sebastian-carpenter sebastian-carpenter removed their assignment Jun 11, 2026
@sebastian-carpenter sebastian-carpenter added the For This Release Release version 5.9.2 label Jun 11, 2026
@sebastian-carpenter sebastian-carpenter self-assigned this Jun 11, 2026

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Code Review

Scan type: reviewOverall recommendation: REQUEST_CHANGES
Findings: 6 total — 6 posted, 0 skipped
5 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [High] Servers with WOLFSSL_SNI_ABORT_ON_ABSENCE now abort every ECH handshake at the outer ClientHello, even when a valid inner SNI is presentsrc/tls.c:2520-2557
  • [Medium] test_wolfSSL_Tls13_ECH_no_private_name ABORT_ON_ABSENCE case encodes the outer-parse abort; missing test for ABORT_ON_ABSENCE with inner SNI presenttests/api.c:14625-14639
  • [Low] BAD_STATE_E restore-failure paths log with WOLFSSL_MSG only, no WOLFSSL_ERROR_VERBOSEsrc/tls.c:16900-16907, src/tls.c:17117-17124
  • [Low] Inline reimplementation of TLSX_SetResponse in TLSX_SNI_Parsesrc/tls.c:2550-2557
  • [Low] EchCheckAcceptance calls TLSX_EchReplaceExtensions even when EchCalcAcceptance failedsrc/tls13.c:5310-5314
  • [Info] PUB_NAME changed to example.com for all interop scenarios, not just the new reject tests.github/scripts/openssl-ech.sh:97-99

Review generated by Skoll

Comment thread src/tls.c
Comment thread tests/api.c
Comment thread src/tls.c Outdated
Comment thread src/tls13.c
Comment thread .github/scripts/openssl-ech.sh

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See these skoll review items -> #10568 (review)

@sebastian-carpenter sebastian-carpenter removed their assignment Jun 12, 2026
@dgarske dgarske self-requested a review June 12, 2026 15:24

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Code Review

Scan type: reviewOverall recommendation: COMMENT
Findings: 2 total — 2 posted, 0 skipped
2 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] ECH reconciliation block in TLSX_Parse omits the TLS1.3 version guard used by sibling SNI logicsrc/tls.c:18715-18717
  • [Low] Duplicated client/server invocation block in openssl-ech.sh reject branches.github/scripts/openssl-ech.sh:175-197,275-307

Review generated by Skoll

Comment thread src/tls.c
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
/* Reconcile ECH inner/outer extensions before verifying SNI so the verify
* pass sees the authoritative list */
if (ret == 0 && msgType == client_hello && isRequest &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] ECH reconciliation block in TLSX_Parse omits the TLS1.3 version guard used by sibling SNI logic

The new ECH accept/reject reconciliation block runs whenever msgType == client_hello && isRequest && !echProcessingInner && ssl->ctx->echConfigs != NULL && !ssl->options.disableECH. Unlike the parallel logic this PR added in TLSX_SNI_Parse (line ~2517, which explicitly checks IsAtLeastTLSv1_3(ssl->version) with the comment "A TLS 1.2 hello never gets an inner pass..."), this block has no version guard. It is currently benign only because the server's TLSX_ECH is created exclusively under IsAtLeastTLSv1_3 in TLSX_PopulateExtensions (so ech resolves to NULL for a downgraded TLS 1.2 ClientHello and the block no-ops). Relying on that implicit invariant is more fragile than the explicit check used a few thousand lines earlier in the same file. Adding the guard would make the two ECH code paths consistent and self-documenting.

Fix: Add IsAtLeastTLSv1_3(ssl->version) to the guard so the reconciliation block matches the defensive version check the PR added to TLSX_SNI_Parse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In retrospect this guard never made sense to add anyways. Removed the two instances I previously added.

Comment thread .github/scripts/openssl-ech.sh Outdated

grep -q "ech_success=1" "$TMP_LOG"
# test with wolfssl client
if [ "$REJECT" -ne 0 ]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] Duplicated client/server invocation block in openssl-ech.sh reject branches

Both openssl_server() and openssl_client() now wrap the test invocation in an if [ "$REJECT" -ne 0 ]; then ... else ... fi where the only differences between the two branches are || true and the expected grep string. The full multi-line $WOLFSSL_CLIENT / $OPENSSL s_client command is copy-pasted in each branch, so a future flag change must be made in two places. This is CI-only scripting (not src/*.c), so it is purely a maintainability nit.

Fix: Optionally de-duplicate the invocation to a single command and branch only on the success assertion. Not blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deduplicated

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#10568 (review)
Make sure you rebase to latest master to pull in CI optimizations.

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Multi-Scan Review

Modes: review + review-securityOverall recommendation: COMMENT
Findings: 4 total — 4 posted, 0 skipped
3 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] [review] Server now hard-rejects an accepted ECH handshake when the outer ClientHello SNI did not match a publicNamesrc/tls.c:18724-18741
  • [Medium] [review] New UNKNOWN_SNI_HOST_NAME_E error path is not exercised by testssrc/tls.c:18727-18734
  • [Low] [review] Duplicated install/restore swap boilerplate in TLSX_GetSizeWithEch and TLSX_WriteWithEchsrc/tls.c:16911-16935, 17076-17152
  • [Info] [review-security] CTX-level private SNI suppression in ECH outer hello relies on implicit semaphore ordering and is untestedsrc/tls.c:16896-16937 (TLSX_GetSizeWithEch), src/tls.c:17062-17153 (TLSX_WriteWithEch)

Review generated by Skoll

Comment thread src/tls.c
ech = (WOLFSSL_ECH*)echX->data;

if (ech != NULL) {
if (ech->state == ECH_WRITE_NONE && ech->innerClientHello != NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] Server now hard-rejects an accepted ECH handshake when the outer ClientHello SNI did not match a publicName · Behavior Change / Interop

The reconciliation block introduces a new, stricter requirement: when the server successfully decrypts the inner ClientHello on the first flight (state == ECH_WRITE_NONE && innerClientHello != NULL and serverState < SERVER_HELLO_RETRY_REQUEST_COMPLETE), it returns UNKNOWN_SNI_HOST_NAME_E unless ech->extensions carries a TLSX_SERVER_NAME (i.e. the outer SNI matched a configured echConfig->publicName). The old EchStateSNI machinery did not gate ECH acceptance on the outer SNI matching the public name. A peer that omits the outer SNI entirely, or sends an outer SNI that does not match any publicName, will now be rejected even though the inner decrypted successfully and ECH could be accepted. wolfSSL's own client always sends the public name (set in TLSX_ECH_Use), so wolfSSL<->wolfSSL and the tests pass, but this is stricter than the ECH spec requires (the public_name is a SHOULD on the client) and may affect interop with third-party clients.

Fix: Confirm this hard-fail-on-missing-public-name is the intended policy. If interop with clients that send a different/absent outer SNI must be preserved, consider downgrading to a permissive path (accept ECH and route on the inner SNI) rather than returning UNKNOWN_SNI_HOST_NAME_E.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is stricter but probably for the better. A client that sends a different public name would stick-out (bad! I don't want to encourage that). The option of omitting the SNI is an interesting idea but is another potential method of fingerprinting..

If someone needs it to be more lenient in the future then an API could be added at that time.

Comment thread src/tls.c
if (ech->state == ECH_WRITE_NONE && ech->innerClientHello != NULL) {
/* The outer ClientHello must have carried the echConfig
* publicName as its SNI */
if (ssl->options.serverState <

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] New UNKNOWN_SNI_HOST_NAME_E error path is not exercised by tests · Missing Tests

The PR adds a new server-side rejection path that returns UNKNOWN_SNI_HOST_NAME_E when the outer ClientHello does not carry a public-name SNI on the first flight, yet none of the added/updated tests drive a ClientHello whose outer SNI is missing or does not match a publicName while the inner still decrypts. All new ECH tests (test_wolfSSL_Tls13_ECH_wire_sni, the bad_configs_ex scenarios) rely on the wolfSSL client which always emits the public-name SNI, so this branch is never taken. Error paths introduced by a PR should be covered.

Fix: Add a test that produces an outer ClientHello with no public-name SNI (or a non-matching outer SNI) while the inner decrypts, and assert the handshake fails with UNKNOWN_SNI_HOST_NAME_E. This both documents and locks in the intended behavior of finding #1.

@sebastian-carpenter sebastian-carpenter Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UNKNOWN_SNI_HOST_NAME was actually the wrong thing to report in this case, changed it to INVALID_PARAMETER so it sends illegal_parameter (as the RFC desires: section 7.1).

Added a test: test_wolfSSL_Tls13_ECH_outer_sni_mismatch

Comment thread src/tls.c Outdated
WC_FREE_VAR_EX(serverName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
if (ret == 0 && r != 0)
ret = r;
if (installed) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] Duplicated install/restore swap boilerplate in TLSX_GetSizeWithEch and TLSX_WriteWithEch · Code Duplication / Maintainability

Both functions now repeat the same pattern: find ECH, if (TLSX_EchShouldHideInner(ech)) { prepended = TLSX_EchSwapExtensions(...); installed = 1; } before the work, then if (installed) { prepended = TLSX_EchSwapExtensions(..., prepended); if (ret == 0 && prepended != 0) ret = BAD_STATE_E; } after. This install/restore-with-BAD_STATE_E-check boilerplate is identical in both call sites and is easy to get subtly out of sync when one is edited.

Fix: Consider small helpers (e.g. TLSX_EchInstallPublic/TLSX_EchRestorePublic) to share the install/restore-and-verify logic between the size and write paths. Optional cleanup; behavior is correct as written.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added helpers TLSX_EchConcealExtensions, TLSX_EchExposeExtensions

- *_wire_sni test is now more efficient
- openssl-ech workflow now does interop with ECH rejection

extra improvements:
- tested TLSX_EchSwapExtensions
- added ctx level SNI to padding calculation
- Improvement of SNI handling for ECH
- Changed EchSwapExtensions to append instead of prepend

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skoll Multi-Scan Review

Modes: review + review-securityOverall recommendation: COMMENT
Findings: 3 total — 3 posted, 0 skipped
3 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] [review+review-security] ECH outer ClientHello reconciliation may falsely reject valid handshakes (new public-name SNI gate)src/tls.c:18766-18784
  • [Low] [review] Redundant TLSX_Find(TLSX_ECH) lookups in TLSX_SNI_Parsesrc/tls.c:2456-2458, 2513-2519
  • [Info] [review] ForceZero used to zero-initialize a non-secret Hpke structsrc/tls.c:13890

Review generated by Skoll

Comment thread src/tls.c
if (echX != NULL)
ech = (WOLFSSL_ECH*)echX->data;

if (ech != NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] ECH outer ClientHello reconciliation may falsely reject valid handshakes (new public-name SNI gate) · Logic

Both the review and review-security modes flagged the new outer-CH reconciliation gate at the end of TLSX_Parse, which returns INVALID_PARAMETER when ECH is accepted (ech->state == ECH_WRITE_NONE && ech->innerClientHello != NULL, serverState < SERVER_HELLO_RETRY_REQUEST_COMPLETE) but no TLSX_SERVER_NAME was recorded on ech->extensions. The review mode identifies a concrete bug (Medium/SUGGEST): when the server itself configures wolfSSL_UseSNI() with a host name equal to the ECH public name, TLSX_SNI_Parse sets matched=1 first and skips the publicName block (workingConfig stays NULL), so the SNI is routed to ssl->extensions instead of ech->extensions; the reconciliation then finds no SNI on ech->extensions and aborts an otherwise valid ECH-accepted handshake. This combination is plausible because the public name is typically a real fronting host with a matching cert. The review-security mode rates the same gate Info (an intentional, tested tightening per RFC 9849 via test_wolfSSL_Tls13_ECH_outer_sni_mismatch) and notes there is no security/DoS impact, since only the offending client's own handshake fails and it is not attacker-amplifiable; it flags that spec-compliant peers omitting the outer SNI or using an empty public_name will now be rejected. The two severity views differ (Medium bug vs Info awareness); the stricter Medium is retained.

Fix: Relax the reconciliation check to also accept a public-name SNI recorded on ssl->extensions, or make TLSX_SNI_Parse always route a publicName-matching SNI to ech->extensions even when a server-configured SNI already matched (run the publicName check even when matched is true). Confirm the stricter outer-SNI requirement is the intended policy for all supported ECH peers (including those that omit the outer SNI or use an empty public_name); if broader interop leniency is later desired, skip the hard abort when no server-side SNI policy is configured. Add a regression test for a server that configures wolfSSL_UseSNI() with a host name equal to the ECH public name and accepts ECH.

Comment thread src/tls.c
/* No server SNI configured: when ECH is active the outer SNI still
* needs to be parsed so it can be matched against the echConfig
* publicName and recorded on ech->extensions. */
if (!ssl->options.disableECH && !ssl->options.echProcessingInner &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] Redundant TLSX_Find(TLSX_ECH) lookups in TLSX_SNI_Parse · Style

The ECH extension is looked up twice on the same ssl->extensions list within one call: once to set checkPublic (TLSX_Find(ssl->extensions, TLSX_ECH) != NULL) and again later to populate echX/ech for the publicName comparison. The earlier lookup discards the result. Caching the echX/ech pointer once would avoid the duplicate list walk and make the two ECH-dependent branches share state.

Fix: Resolve echX/ech once near the top of the server block and reuse it for both the checkPublic decision and the publicName comparison. Optional cleanup; not a correctness issue.

Comment thread src/tls.c
XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER);
return MEMORY_E;
}
ForceZero(ech->hpke, sizeof(Hpke));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ [Info] ForceZero used to zero-initialize a non-secret Hpke struct · Convention

ForceZero(ech->hpke, sizeof(Hpke)) is added to zero the freshly-XMALLOC'd Hpke struct before wc_HpkeInit. ForceZero is intended for wiping secrets so the compiler cannot elide it; using it for plain initialization is unusual (XMEMSET is the conventional init). This is harmless and consistent with the surrounding ForceZero(ech, sizeof(WOLFSSL_ECH)), so it is cosmetic. The unconditional wc_HpkeFreeKey() call (NULL ephemeralKey) introduced alongside it is safe because wc_ecc_key_free/wc_curve25519_free both null-check.

Fix: Use XMEMSET(ech->hpke, 0, sizeof(Hpke)) for initialization, reserving ForceZero for secret wiping. Take it or leave it; no functional impact.

@dgarske dgarske requested a review from Copilot June 12, 2026 21:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

tests/api.c:1

  • This helper will underflow storage-- when called with count == 0, causing an out-of-bounds write to storage->next. Even if current call sites always pass count > 0, it’s brittle for future edits. Add an early return when count <= 0.

Comment thread src/tls.c
Comment on lines +16840 to +16843
void TLSX_EchReplaceExtensions(WOLFSSL* ssl, byte accepted)
{
int ret = 0;
TLSX* echX;
WOLFSSL_ECH* ech;
Comment thread src/tls.c
void TLSX_SetResponse(WOLFSSL* ssl, TLSX_Type type);
/** Mark an extension to be sent back to the client. */
void TLSX_SetResponse(WOLFSSL* ssl, TLSX_Type type)
void TLSX_SetResponseInList(TLSX* list, TLSX_Type type);
Comment thread src/tls.c
Comment on lines +1744 to +1746
void TLSX_SetResponseInList(TLSX* list, TLSX_Type type)
{
TLSX *extension = TLSX_Find(ssl->extensions, type);
TLSX *extension = TLSX_Find(list, type);
Comment thread src/tls.c
Comment thread src/tls.c
Comment on lines 14960 to +14962
if (ech->hpke != NULL) {
if (ech->ephemeralKey != NULL)
wc_HpkeFreeKey(ech->hpke, ech->hpke->kem, ech->ephemeralKey,
ech->hpke->heap);
wc_HpkeFreeKey(ech->hpke, ech->hpke->kem, ech->ephemeralKey,
ech->hpke->heap);
Comment thread .github/scripts/openssl-ech.sh
@dgarske

dgarske commented Jun 12, 2026

Copy link
Copy Markdown
Member

This PR has many changes too late in release process. There are a few minor good things that @sebastian-carpenter is going to pull out into a smaller PR to see if it can go into the release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants